#include "LireMartiales.h"

#include "ElementDOM.h"

#include "ATL.h"

#include "lib.h"

#include <boost/assign/list_of.hpp>

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

#include <boost/foreach.hpp>

using namespace std;
using namespace boost;
using namespace boost::lambda;
using namespace boost::assign;

/**
 * Instance utilisée comme "objet statique", vu qu'apparemment un
 * objet statique n'a pas ses champs statiques définis correctement
 * lors de son initialisation.
 */
//LireMartiales listeArmesXML;

/**
 * Fonction qui permet d'initialiser l'objet \c listeArmesXML à la
 * demande. Si ledit objet a déjà été initialisé, la fonction ne fait
 * rien.
 *
 * @return l'adresse listeArmesXML, ou 0 si un problème apparaît lors
 * de la création de l'objet.
 */
// const LireMartiales * initLectureArmesXML()
// {
//   if ( listeArmesXML.initialisationOK() )
//     return &listeArmesXML;

//   StatusCode sc( listeArmesXML . init() );

//   if ( ! sc )
//     {
//       sc.afficher();
//       FlotMessage err( ERR );
//       err << "Impossible de récupérer la liste d'armes" << endmsg;
//       return 0;
//     }

//   return &listeArmesXML;
// }

bool LireMartiales::niveauxLus( false );

const vector< string > LireMartiales::NomsNiveaux = list_of( "LireMartiales::elementaire" );

DebugLevelType LireMartiales::elementaire( DEBUG );

/**
 * Constructeur par défaut, construit le parseur et lit le niveau de
 * sortie si aucune autre instance ne l'a déjà fait.
 */
LireMartiales::LireMartiales()
  : ADDObjetBase( "LireMartiales" ),
    parser( "Joueur0/Donnees/ListeArmes.xml" ), InitialisationOK( false )
{
  if ( ! niveauxLus )
    {
      lireNiveaux( this, list_of( "LireMartiales::elementaire" )/*NomsNiveaux*/ );
      niveauxLus = true;
    }
}

/**
 * Le destructeur n'a rien de spécial à faire.
 */
LireMartiales::~LireMartiales()
{}

/**
 * Méthode qui termine l'initialisation de la classe. Réalise en
 * premier lieu la lecture du fichier XML, puis la création de la
 * structure DOM du document. Ces deux étapes peuvent renvoyer un
 * StatusCode en cas d'échec.
 *
 * Ensuite la liste d'armes est créée.
 *
 * @return StatusCode.
 */
StatusCode LireMartiales::init()
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "LireMartiales::init -> début" << endmsg;
  StatusCode sc( parser . chargerFichier() );

  if ( ! sc )
    {
      error() << sc.message() << endmsg;
      func() << "LireMartiales::init -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return sc;
    }

  sc = parser . lireDocument();

  if ( ! sc )
    {
      error() << sc.message() << endmsg;
      func() << "LireMartiales::init -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return sc;
    }

  parser.afficherArbre( blank() );
  
  const ElementDOM * liste = parser . premierEnfant();

  vector< const ElementDOM * > larges = trouverGroupes( liste ),
    restreint, armes, alts;

  const ElementDOM * el, * el2, * el3, * el4;

  GroupeInfo infosGrp;

  GLarge donneesGroupe;
  vector< string > nomsRestreints;

  BOOST_FOREACH( el, larges )
    {
      initGrpInfo( infosGrp );
      nomsRestreints.clear();
      
      infosGrp . NomGrp = trouverNomGroupe( el );

      if ( ! lireString( el -> attribut( "num" ), infosGrp . NumGrpLarge ) )
	{
	  error() << "Ne peut pas lire le numéro de groupe large pour "
		  << infosGrp . NomGrp << endmsg;
	  continue;
	}

      donneesGroupe.Nom = infosGrp . NomGrp;

      linfo()  << infosGrp . NomGrp << ", num = " << infosGrp . NumGrpLarge
	       << " (" << el -> balise() << ')' << endmsg;
      
      if ( el -> balise() == "GroupeLarge" )
	{
	  linfo() << "C'est un groupe large" << endmsg;
	  restreint = trouverGroupes( el );
	  BOOST_FOREACH( el2, restreint )
	    {
	      nomsRestreints.push_back( trouverNomGroupe( el2 ) );
	      if ( ! lireString( el2 -> attribut( "num" ),
				 infosGrp . NumGrpRestreint ) )
		{
		  error() << "Ne peut pas lire le numéro de groupe restreint pour "
			  << infosGrp . NomGrp << endmsg;
		  continue;
		}

	      linfo() << "\t" << trouverNomGroupe( el2 )
		      << ", num = " << infosGrp . NumGrpRestreint << endmsg;
	      el3 = parser . premierEnfant( el2 );
	      el3 = parser . prochainFrere( el3 );
	      armes = trouverArmes( el3 );
	      BOOST_FOREACH( el3, armes )
		{
		  ListeArmes . push_back( lireArme( el3, alts, infosGrp ) );
		  BOOST_FOREACH( el4, alts )
		    {
		      ListeArmes . push_back( lireArme( el4, alts,
							infosGrp ) );
		    }    
		}
	    }
	  donneesGroupe.Restreints = nomsRestreints.size();
	  donneesGroupe.NomsRestreints.assign( nomsRestreints.begin(),
					       nomsRestreints.end() );
	}
      else if ( el -> balise() == "GroupeRestreint" )
	{
	  linfo() << "C'est un groupe restreint." << endmsg;
	  donneesGroupe.Nom = infosGrp . NomGrp;
	  donneesGroupe.Restreints = 0;
	  donneesGroupe.NomsRestreints.clear();
	  el2 = parser . premierEnfant( el );
	  el2 = parser . prochainFrere( el2 );
	  armes = trouverArmes( el2 );
	  BOOST_FOREACH( el3, armes )
	    {
	      ListeArmes . push_back( lireArme( el3, alts, infosGrp ) );
	      BOOST_FOREACH( el4, alts )
		{
		  ListeArmes . push_back( lireArme( el4, alts,
						    infosGrp ) );
		}
	    }
	}
      GroupesLarges.push_back( donneesGroupe );
      lfunc() << "Ajout du groupe ('large') " << donneesGroupe . Nom << endmsg;
    }

  info() << "Nombre de groupes 'larges' : " << GroupesLarges.size() << endmsg;

  InitialisationOK = true;

  func() << "LireMartiales::init -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return sc;
}


const bool& LireMartiales::initialisationOK() const
{
  return InitialisationOK;
}

/**
 * Permet de récupérer un pointeur sur la liste d'armes.
 *
 * @return l'adresse de ListeArmes.
 */
const vector< Martiale > * LireMartiales::listeArmes() const
{
  return &ListeArmes;
}

/**
 * Permet de récupérer un pointeur sur la liste des groupes larges.
 *
 * @return l'adresse de GroupesLarges.
 */
const vector< GLarge > * LireMartiales::groupesLarges() const
{
  return &GroupesLarges;
}

/**
 * Gestion du niveau de sortie des fonctions.
 *
 * @param[in] level le niveau.
 */

void LireMartiales::setLevel( const unsigned & level, const unsigned& )
{
  elementaire = static_cast< DebugLevelType >( level );
}

/**
 * Permet de réinitialiser proprement une structure GroupeInfo. Par
 * réinitialisation on entend mettre 0 pour les numéros de groupe et
 * un nom vide.
 *
 * @param[out] grpInfo l'instance de GroupeInfo à réinitialiser.
 */
void LireMartiales::initGrpInfo( GroupeInfo& grpInfo )
{
  grpInfo . NomGrp = "";
  grpInfo . NumGrpLarge = 0;
  grpInfo . NumGrpRestreint = 0;
}

/**
 * Méthode permettant de trouver les ElementDOM étant des groupes
 * (larges ou restreints) qui sont les descendants d'un ElementDOM
 * donné.
 *
 * Si l'argument est l'élément racine, alors on obtient les groupes
 * dits larges (càd les groupes larges et les groupes
 * restreint). Lorsque l'argument est un groupe large, on obtient la
 * liste des groupes restreints.
 *
 * @param[in] elDom pointeur sur l'ElementDOM dont on veut les
 * descendants de type "groupe".
 *
 * @greturn la liste des enfants de \c elDom.
 */
vector< const ElementDOM * >
LireMartiales::trouverGroupes( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverGroupes -> début" << endmsg;                                
  vector< const ElementDOM * > retour;                                     
                                                                                
  if ( elDom -> balise() == "Selection" )                                       
    {                                                                           
      retour . assign( elDom -> enfants() -> begin(),                           
                       elDom -> enfants() -> end() );                           
    }
  else if ( elDom -> balise() == "GroupeLarge" )                                
    {
      retour . assign( elDom -> enfants() -> begin(),
		       elDom -> enfants() -> end() );

      vector< const ElementDOM * >::const_iterator
        debut( retour.begin() ), fin( retour.end() );

      fin = remove_if( retour.begin(), retour.end(),
                       bind< string >( &ElementDOM::balise, _1 ) == "Nom" );

      retour . assign( debut, fin );
    }
  
  info() << "Trouvé " << retour . size() << " groupes" << endmsg;
  
  func() << "trouverGroupes -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return retour;
}

/**
 * Méthode permettant d'obtenir le nom d'un groupe (restreint ou
 * large) donné.
 *
 * @param[in] elDom pointeur sur l'ElementDOM de type "groupe".
 *
 * @return le nom du groupe, ou alors une chaîne vide si \c elDom
 * n'est pas un groupe.
 */
string LireMartiales::trouverNomGroupe( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverNomGroupe -> début" << endmsg;
  string nomBalise( elDom -> balise() );

  if ( nomBalise.find( "Groupe" ) == string::npos )
    {
      error() << "Tentative de trouver le nom d'un groupe alors que la balise e\
st \'" << nomBalise << "\'" << endmsg;
      func() << "trouverNomGroupe -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return "";
    }
  else if ( parser . premierEnfant( elDom ) -> balise() != "Nom" )
    {
      error() << "Tentative d'extraire le nom du groupe depuis une balise "
	      << "qui n'est pas 'Nom' mais '"
	      << parser . premierEnfant( elDom ) -> balise() << "'" << endmsg;
      depileNiveau();
      return "";
    }

  debug() << "Le nom de groupe trouvé est : '"
	  << parser . premierEnfant( elDom ) -> donnees()
	  << "' fourni par la balise '"
	  << parser . premierEnfant( elDom ) -> balise()
	  << "' fille de '" << elDom -> balise() << "'" << endmsg;

  func() << "trouverNomGroupe -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return parser . premierEnfant( elDom ) -> donnees();
}

/**
 * Méthode permettant de récupérer la liste des armes d'un groupe
 * donné.
 *
 * @param[in] elDom pointeur sur l'ElementDOM "Armes".
 *
 * @return la liste des armes, qui est vide si \c elDom est incorrect.
 */
vector< const ElementDOM * > LireMartiales::trouverArmes( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverArmes -> début" << endmsg;
  vector< const ElementDOM * > retour;

  if ( elDom -> balise() != "Armes" )
    {
      error() << "Tentative de trouver la liste d'armes, mais la balise est \'"
	      << elDom -> balise() << "\' au lieu de \'Armes\'" << endmsg;
      func() << "trouverArmes -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return retour;
    }
  else
    retour . assign( elDom -> enfants() -> begin(),
                     elDom -> enfants() -> end() );

  func() << "trouverArmes -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return retour;
}

/**
 * Méthode permettant de récupérer le nom de l'arme voulue.
 *
 * @param[in] elDom pointeur sur un ElementDOM "Arme".
 *
 * @return le nom de l'arme, ou une chaîne vide si \c elDom est
 * incorrect.
 */
string LireMartiales::trouverNomArme( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverNomArme -> début" << endmsg;
  string nomBalise( elDom -> balise() );

  if ( nomBalise == "Armes" || nomBalise.find( "Arme" ) == string::npos )
    {
      error() << "Tentative de trouver le nom d'une arme alors que la balise es\
t \'" << nomBalise << "\'" << endmsg;
      func() << "trouverNomArme -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return "";
    }

  func() << "trouverNomArme -> fin" << endmsg;
  return parser . premierEnfant( elDom ) -> donnees();
}

/**
 * Méthode qui construit la compétence martiale à partir d'une balise
 * "Arme". Tous les champs nécessaires sont initialisé. La structure
 * du document XML n'est pas vraiment vérifiée, vu que la validation
 * est faite par SchemaArme.xsd.
 *
 * @param[in] elDom pointeur sur un ElementDOM "Arme".
 * @param[in] enPlus liste des compétences martiales "imbriquées"
 * (munitions, 1/2 mains, ...).
 * @param[in] info instance de GroupeInfo permettant de gérer les
 * numéros de groupe large et restreint.
 *
 * @return une instance de Martiale, anonyme en cas d'erreur.
 */
Martiale LireMartiales::lireArme( const ElementDOM * elDom,
				  vector< const ElementDOM * > & enPlus,
				  const GroupeInfo& info )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "lireArme -> début" << endmsg;
  string nomBalise( elDom -> balise() );

  enPlus.clear();

  if ( nomBalise != "Arme" &&
       nomBalise != "ArmeJet" &&
       nomBalise != "ArmeProj" &&
       nomBalise != "Projectile" &&
       nomBalise != "Alternative" &&
       nomBalise != "ArmeChoix" )
    {
      error() << "Tentative de lire une arme, mais la balise est \'" << nomBalise << "\'" << endmsg;
      func() << "lireArme -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return Martiale( "", "", 'P', "", 1, "", "", "", "", false, 1, 2 );
    }
  
  string nom( "" );
  string masse( "" );
  char taille( 0 );
  string type( "-" );
  unsigned vitesse( 0 );
  string freq( "-" );
  string portee( "" );
  string degatsPM( "-" );
  string degatsG( "-" );
  bool afficheProch( false );
  unsigned gLarge( info . NumGrpLarge );
  unsigned gRestreint( info . NumGrpRestreint );

  const ElementDOM * courant;

  if ( nomBalise == "Arme" )
    {
      // Pas besoin de vérifier les balises, la validation fait ça
      // pour nous!
      courant = parser . premierEnfant( elDom );
      nom = courant -> donnees();
      debug() << "L'arme (Arme) trouvée est '" << nom << "'" << endmsg;
      courant = parser . prochainFrere( courant );
      masse = courant -> donnees();
      courant = parser . prochainFrere( courant );
      taille = *( ( courant -> donnees() ) . begin() );
      courant = parser . prochainFrere( courant );
      type = courant -> donnees();
      courant = parser . prochainFrere( courant );
      if ( ! lireString( courant -> donnees(), vitesse ) )
	{
	  func() << "lireArme -> fin" << endmsg;
	  /*Debug::*/depileNiveau();
	  return Martiale( "", "", 'P', "", 1, "", "", "", "",
			   false, -1u, 0 );
	}
      courant = parser . prochainFrere( courant );
      degatsPM = courant -> donnees();
      courant = parser . prochainFrere( courant );
      degatsG = courant -> donnees();

      courant = parser . prochainFrere( courant );

      afficheProch = ( courant != 0 );

      if ( afficheProch )
	enPlus . assign ( courant -> enfants() -> begin(),
			  courant -> enfants() -> end() );
    }
  else if ( nomBalise == "ArmeJet" )
    {
      courant = parser . premierEnfant( elDom );
      nom = courant -> donnees();
      debug() << "L'arme (ArmeJet) trouvée est '" << nom << "'" << endmsg;
      courant = parser . prochainFrere( courant );
      masse = courant -> donnees();
      courant = parser . prochainFrere( courant );
      taille = *( ( courant -> donnees() ) . begin() );
      courant = parser . prochainFrere( courant );
      type = courant -> donnees();
      courant = parser . prochainFrere( courant );
      if ( ! lireString( courant -> donnees(), vitesse ) )
	{
	  func() << "lireArme -> fin" << endmsg;
	  /*Debug::*/depileNiveau();
	  return Martiale( "", "", 'P', "", 1, "", "", "", "",
			   false, -1u, 0 );
	}
      courant = parser . prochainFrere( courant );
      freq = courant -> donnees();
      courant = parser . prochainFrere( courant );
      portee = courant -> donnees();      
      courant = parser . prochainFrere( courant );
      if ( courant -> balise() == "DegatsPM" )
	{
	  degatsPM = courant -> donnees();
	  courant = parser . prochainFrere( courant );
	  degatsG = courant -> donnees();

	  courant = parser . prochainFrere( courant );

	  afficheProch = ( courant != 0 );
	}
      else
	{
	  afficheProch = true;

	  enPlus . assign ( courant -> enfants() -> begin(),
			    courant -> enfants() -> end() );
	}
    }
  else if ( nomBalise == "ArmeProj" )
    {
      courant = parser . premierEnfant( elDom );
      nom = courant -> donnees();
      debug() << "L'arme (ArmeProj) trouvée est '" << nom << "'" << endmsg;
      courant = parser . prochainFrere( courant );
      masse = courant -> donnees();
      courant = parser . prochainFrere( courant );
      taille = *( ( courant -> donnees() ) . begin() );
      courant = parser . prochainFrere( courant );
      if ( ! lireString( courant -> donnees(), vitesse ) )
	{
	  func() << "lireArme -> fin" << endmsg;
	  /*Debug::*/depileNiveau();
	  return Martiale( "", "", 'P', "", 1, "", "", "", "",
			   false, -1u, 0 );
	}
      courant = parser . prochainFrere( courant );
      freq = courant -> donnees();
      courant = parser . prochainFrere( courant );
      afficheProch = ( courant != 0 );

      if ( afficheProch )
	enPlus . assign ( courant -> enfants() -> begin(),
			  courant -> enfants() -> end() );
    }
  else if ( nomBalise == "Projectile" )
    {
      courant = parser . premierEnfant( elDom );
      nom = "- " + courant -> donnees();
      debug() << "L'arme (Projectile) trouvée est '" << nom << "'" << endmsg;
      courant = parser . prochainFrere( courant );
      masse = courant -> donnees();
      courant = parser . prochainFrere( courant );
      taille = *( ( courant -> donnees() ) . begin() );
      courant = parser . prochainFrere( courant );
      type = courant -> donnees();
      courant = parser . prochainFrere( courant );
      portee = courant -> donnees();      
      courant = parser . prochainFrere( courant );
      degatsPM = courant -> donnees();
      courant = parser . prochainFrere( courant );
      degatsG = courant -> donnees();

      courant = parser . prochainFrere( elDom );

      afficheProch = ( courant != 0 );
    }
  else if ( nomBalise == "Alternative" )
    {
      courant = parser . premierEnfant( elDom );
      nom = "- " + courant -> donnees();
      debug() << "L'arme (Alternative) trouvée est '" << nom << "'" << endmsg;
      courant = parser . prochainFrere( courant );
      if ( courant -> balise() == "Masse" )
	{
	  masse = courant -> donnees();
	  courant = parser . prochainFrere( courant );
	}
      if ( courant -> balise() == "Taille" )
	{
	  taille = *( ( courant -> donnees() ) . begin() );
	  courant = parser . prochainFrere( courant );
	}
      if ( courant -> balise() == "Type" )
	{
	  type = courant -> donnees();
	  courant = parser . prochainFrere( courant );
	}
      if ( courant -> balise() == "Vitesse" )
	{
	  if (  ! lireString( courant -> donnees(), vitesse ) )
	    {
	      func() << "lireArme -> fin" << endmsg;
	      /*Debug::*/depileNiveau();
	      return Martiale( "", "", 'P', "", 1, "", "", "", "",
			       false, -1u, 0 );
	    }
	  courant = parser . prochainFrere( courant );
	}
      if ( courant -> balise() == "Frequence" )
	{
	  freq = courant -> donnees();
	  courant = parser . prochainFrere( courant );
	}
      if ( courant -> balise() == "Portee" )
	{
	  portee = courant -> donnees();      
	  courant = parser . prochainFrere( courant );
	}
      degatsPM = courant -> donnees();
      courant = parser . prochainFrere( courant );
      degatsG = courant -> donnees();

      courant = parser . prochainFrere( elDom );

      afficheProch = ( courant != 0 );
    }
  else if ( nomBalise == "ArmeChoix" )
    {
      courant = parser . premierEnfant( elDom );
      nom = courant -> donnees();
      debug() << "L'arme (ArmeChoix) trouvée est '" << nom << "'" << endmsg;
      courant = parser . prochainFrere( courant );
      masse = courant -> donnees();
      courant = parser . prochainFrere( courant );
      taille = *( ( courant -> donnees() ) . begin() );
      courant = parser . prochainFrere( courant );
      type = courant -> donnees();
      courant = parser . prochainFrere( courant );
      if (  ! lireString( courant -> donnees(), vitesse ) )
	{
	  func() << "lireArme -> fin" << endmsg;
	  /*Debug::*/depileNiveau();
	  return Martiale( "", "", 'P', "", 1, "", "", "", "",
			   false, -1u, 0 );
	}
      courant = parser . prochainFrere( courant );

      afficheProch = true;

      enPlus . assign ( courant -> enfants() -> begin(),
			courant -> enfants() -> end() );
    }

  func() << "lireArme -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return Martiale( nom, "$" + masse + "$", taille, type, vitesse,
		   "$" + freq + "$", portee,
		   "$" + degatsPM + "$", "$" + degatsG + "$",
		   afficheProch, gLarge, gRestreint );
}
